package com.livepan.server.service.impl;

import com.livepan.server.dao.*;
import com.livepan.server.dataobject.*;
import com.livepan.server.error.BusinessException;
import com.livepan.server.error.EmBusinessError;
import com.livepan.server.service.ContestService;
import com.livepan.server.service.ProblemService;
import com.livepan.server.service.SubmissionService;
import com.livepan.server.service.UserService;
import com.livepan.server.service.model.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class ContestServiceImpl implements ContestService {
    @Autowired
    private ContestDOMapper contestDOMapper;

    @Autowired
    private ProblemService problemService;

    @Autowired
    private ContestProblemDOMapper contestProblemDOMapper;

    @Autowired
    private AnnouncementDOMapper announcementDOMapper;

    @Autowired
    private OpenContestDOMapper openContestDOMapper;

    @Autowired
    private SubmissionService submissionService;

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionDOMapper permissionDOMapper;

    @Override
    public ContestModel getContestById(Integer contestId, String userId) throws BusinessException {
        ContestDO contestDO = contestDOMapper.queryContestById(contestId);
        return convertContestModelFromContestDO(contestDO);
    }

    @Override
    public List<ContestModel> queryContestList(String keyword, Integer offset, Integer limit) {
        List<ContestDO> contestDOList = contestDOMapper.queryContestList(keyword, offset, limit);
        List<ContestModel> contestModelList = contestDOList.stream()
                .map(this::convertContestModelFromContestDO).collect(Collectors.toList());
        return contestModelList;
    }

    @Override
    public Integer queryContestCount(String keyword) {
        return contestDOMapper.queryContestCount(keyword);
    }


    @Override
    public List<ContestProblemModel> getProblemList(Integer contestId, String userId)
            throws BusinessException {
        checkContestReadPermission(contestId, userId);


        List<ContestProblemDO> problemDOList = contestProblemDOMapper.getProblemList(contestId);
        List<ContestProblemModel> contestProblemModelList = problemDOList.stream()
                .map(this::convertContestProblemModelFromContestProblemDO).collect(Collectors.toList());

        //todo 有待优化
        List<SubmissionModel> userContestSubmission = submissionService
                .getContestSubmission(contestId, userId);
        for (SubmissionModel submissionModel : userContestSubmission) {
            for (ContestProblemModel contestProblemModel : contestProblemModelList) {
                if (Objects.equals(contestProblemModel.getProblemId(), submissionModel.getProblemId())) {
                    contestProblemModel.setSubmitted(true);
                }
            }
        }

        return contestProblemModelList;
    }

    @Override
    public List<AnnouncementModel> getAnnouncementList(Integer contestId, String userId) throws BusinessException {
        checkContestReadPermission(contestId, userId);
        List<AnnouncementDO> announcementDOList = announcementDOMapper.getAnnouncementList(contestId);
        List<AnnouncementModel> announcementModelList = announcementDOList.stream()
                .map(this::convertAnnouncementModelFromAnnouncementDO).collect(Collectors.toList());
        return announcementModelList;
    }

    @Override
    public List<ContestRankModel> getContestRankList(Integer contestId, String userId) throws BusinessException {
        checkContestReadPermission(contestId, userId);
        System.out.println("1:" + System.currentTimeMillis());
        List<SubmissionModel> submissionModelList = submissionService.getContestSubmission(contestId, userId);

        //这里时间从大到小排序，然后再放到set里面去重
        submissionModelList.sort((o1, o2) -> o2.getSubmitTime().compareTo(o1.getSubmitTime()));


        Set<SubmissionModel> submissionModelSet = new HashSet<>(submissionModelList);

        System.out.println("2:" + System.currentTimeMillis());
        Set<String> userSet = new HashSet<>();
        for (SubmissionModel submissionModel : submissionModelSet) {
            userSet.add(submissionModel.getUserId());
        }
        List<ContestProblemDO> contestProblemDOList = contestProblemDOMapper.getProblemList(contestId);
        int problemCount = contestProblemDOList.size();

        Map<Integer, ContestProblemDO> contestProblemDOMap = new HashMap<>();

        for (ContestProblemDO contestProblemDO : contestProblemDOList) {
            contestProblemDOMap.put(contestProblemDO.getProblemId(), contestProblemDO);
        }


        System.out.println("3:" + System.currentTimeMillis());
        Map<String, ContestRankModel> rankModelMap = new HashMap<>();
        //生成用户id-成绩排名对象
        for (String userid : userSet) {
            ContestRankModel contestRankModel = new ContestRankModel();
            contestRankModel.setUserId(userid);
            contestRankModel.setName(userService.getUserById(userid).getName());
            List<ContestRankModel.Info> info = new ArrayList<>(problemCount);
            for (int i = 0; i < problemCount; i++) {
                info.add(new ContestRankModel.Info());
            }
            contestRankModel.setProgress(info);
            rankModelMap.put(userid, contestRankModel);
        }
        System.out.println("4:" + System.currentTimeMillis());
        for (SubmissionModel submissionModel : submissionModelSet) {
            ContestRankModel contestRankModel = rankModelMap.get(submissionModel.getUserId());
            if (submissionModel.getScore() == 100) {
                contestRankModel.setAccept(contestRankModel.getAccept() + 1);
            }
            contestRankModel.setScore(contestRankModel.getScore() + submissionModel.getScore());

            //todo 设置rank具体情况
            //1 通过problemId得到是比赛的第几个题目
            ContestProblemDO contestProblemDO = contestProblemDOMap.get(submissionModel.getProblemId());
            int num = contestProblemDO.getNum();

            //2. 设置第几个的情况
            contestRankModel.getProgress().get(num - 1).score = submissionModel.getScore();
            contestRankModel.getProgress().get(num - 1).status = submissionModel.getResult();
        }

        System.out.println("5:" + System.currentTimeMillis());
        List<ContestRankModel> rankModelList = new ArrayList<>(rankModelMap.values());

        //按照成绩排序
        rankModelList.sort((o1, o2) -> o2.getScore() - o1.getScore());

        //进行排名
        int size = rankModelList.size();
        for (int i = 0; i < size; i++) {
            if (i == 0 || !rankModelList.get(i).getScore().equals(rankModelList.get(i - 1).getScore())) {
                rankModelList.get(i).setRank(i + 1);
            } else {
                rankModelList.get(i).setRank(rankModelList.get(i - 1).getRank());
            }
        }
        System.out.println("6:" + System.currentTimeMillis());
        return rankModelList;
    }

    @Override
    public List<Integer> getContestProblemIdList(Integer contestId, String userId) throws BusinessException {
        checkContestReadPermission(contestId, userId);
        List<ContestProblemDO> contestProblemDOList = contestProblemDOMapper.getProblemList(contestId);

        List<Integer> problemList = new ArrayList<>();

        contestProblemDOList.sort(Comparator.comparingInt(ContestProblemDO::getNum));

        for (ContestProblemDO contestProblemDO : contestProblemDOList) {
            problemList.add(contestProblemDO.getProblemId());
        }

        return problemList;
    }

    @Override
    public ContestModel createOrUpdateContest(ContestModel model, String userId) throws BusinessException {
        //todo 鉴权
        checkContestUpdatePermission(model.getContestId(), userId);

        Integer contestId = model.getContestId();
        ContestDO contestDO = convertContestDOFromContestModel(model);
        int count;
        if (contestId != null && getContestById(contestId, userId) != null) {
            count = contestDOMapper.updateByPrimaryKeySelective(contestDO);
        } else {
            count = contestDOMapper.insertSelective(contestDO);
        }

        deleteContestProblem(contestId, null);
        for (int i = 0; i < model.getContestProblemList().size(); i++) {
            ContestProblemModel contestProblemModel = model.getContestProblemList().get(i);
            String title = contestProblemModel.getTitle();
            if (title == null || title.equals("")) {
                title = problemService.getProblemByID(contestProblemModel.getProblemId()).getTitle();
            }

            insertContestProblem(contestId, userId,
                    contestProblemModel.getProblemId(), i + 1, title);
        }

        System.out.println("createOrUpdateContest 成功了:" + count);
        return getContestById(contestDO.getContestId(), userId);
    }

    @Override
    public void deleteContestProblem(Integer contestId, Integer problemId) throws BusinessException {
//        checkContestUpdatePermission(contestId, problemId);

        int count;

        if (problemId == null) {
            count = contestProblemDOMapper.deleteAllProblem(contestId);
        } else {
            ContestProblemDOKey contestProblemDOKey = new ContestProblemDOKey();
            contestProblemDOKey.setProblemId(problemId);
            contestProblemDOKey.setContestId(contestId);
            count = contestProblemDOMapper.deleteByPrimaryKey(contestProblemDOKey);
        }

    }

    @Override
    public int insertContestProblem(Integer contestId, String userId, Integer problemId, Integer num, String title) throws BusinessException {
        //todo 这里可能会有权限问题
        checkContestUpdatePermission(contestId, userId);

        ContestProblemDO contestProblemDO = new ContestProblemDO();
        contestProblemDO.setContestId(contestId);
        contestProblemDO.setProblemId(problemId);
        contestProblemDO.setNum(num);
        contestProblemDO.setTitle(title);
        int count = contestProblemDOMapper.insertSelective(contestProblemDO);
        return count;
    }

    @Override
    public void checkContestReadPermission(Integer contestId, String userId) throws BusinessException {
        //管理员和出题人因为需要进行管理，所以任何时候都是能够阅读和提交的
        if (permissionDOMapper.selectByUserId(userId).contains(1)) {
            return;
        }

        //权限判断

        //时间判断
        ContestDO contestDO = contestDOMapper.selectByPrimaryKey(contestId);
        Long userOpenContestTime = getUserOpenContestTime(contestId, userId, 1);
        Long startTime = contestDO.getStartTime();
        Long now = System.currentTimeMillis();
        if (now < startTime || userOpenContestTime == null) {
            throw new BusinessException(EmBusinessError.CONTEST_PENDING_ERROR);
        }
    }

    @Override
    public void checkContestSubmitPermission(Integer contestId, String userId) throws BusinessException {
        //管理员和出题人因为需要进行管理，所以任何时候都是能够阅读和提交的
        if (permissionDOMapper.selectByUserId(userId).contains(1)) {
            return;
        }

        //1. 检查是否可读
        checkContestReadPermission(contestId, userId);

        //2. 比赛结束后不能进行提交
        Long now = System.currentTimeMillis();
        ContestDO contestDO = contestDOMapper.selectByPrimaryKey(contestId);
        Long userOpenContestTime = getUserOpenContestTime(contestId, userId, 1);
        Long userCloseContestTime = getUserOpenContestTime(contestId, userId, 2);
        if (now > contestDO.getEndTime() || userCloseContestTime != null
                || now - userOpenContestTime > 7200000) {
            throw new BusinessException(EmBusinessError.CONTEST_END_ERROR);
        }
    }

    @Override
    public void checkContestUpdatePermission(Integer contestId, String userId) throws BusinessException {
        //1. 管理员
        if (permissionDOMapper.selectByUserId(userId).contains(1)) {
            return;
        }


        // 2. 题目创建人  3. 老师或其他人 todo


    }

    @Override
    public Long getUserOpenContestTime(Integer contestId, String userId, Integer recordType) throws BusinessException {
        OpenContestDO openContestDO = new OpenContestDO();
        openContestDO.setUserId(userId);
        openContestDO.setContestId(contestId);
        openContestDO.setRecordType(recordType);
        openContestDO = openContestDOMapper.selectByOpenContestDO(openContestDO);
        if (openContestDO == null) {
            return null;
        } else {
            return openContestDO.getMillisecond();
        }
    }

    @Override
    public synchronized Boolean setUserOpenContestTime(@NotNull Integer contestId, @NotNull String userId
            , @NotNull Integer recordType, @NotNull Long millisecond) throws BusinessException {
        if (getUserOpenContestTime(contestId, userId, recordType) != null) {
            return false;
        }


        OpenContestDO openContestDO = new OpenContestDO();
        openContestDO.setContestId(contestId);
        openContestDO.setUserId(userId);
        openContestDO.setRecordType(recordType);
        openContestDO.setMillisecond(millisecond);

        int count = openContestDOMapper.insertSelective(openContestDO);
        if (count == 0) {
            return false;
        } else {
            return true;
        }
    }


    private ContestModel convertContestModelFromContestDO(ContestDO contestDO) {
        if (contestDO == null) {
            return null;
        }

        ContestModel contestModel = new ContestModel();
        BeanUtils.copyProperties(contestDO, contestModel);
        return contestModel;
    }

    private ContestDO convertContestDOFromContestModel(ContestModel contestModel) {
        if (contestModel == null) {
            return null;
        }

        ContestDO contestDO = new ContestDO();
        BeanUtils.copyProperties(contestModel, contestDO);
        return contestDO;
    }

    private ContestProblemModel convertContestProblemModelFromContestProblemDO(ContestProblemDO contestProblemDO) {
        if (contestProblemDO == null) {
            return null;
        }

        ContestProblemModel contestProblemModel = new ContestProblemModel();
        BeanUtils.copyProperties(contestProblemDO, contestProblemModel);

        return contestProblemModel;
    }

    private AnnouncementModel convertAnnouncementModelFromAnnouncementDO(AnnouncementDO announcementDO) {
        if (announcementDO == null) {
            return null;
        }

        AnnouncementModel announcementModel = new AnnouncementModel();
        BeanUtils.copyProperties(announcementDO, announcementModel);
        return announcementModel;
    }
}
